/**
 * @file def.h
 * @date 230425
 * @author xin
 * @brief 模块文件间的接口（函数声明），其他文件不会调用的函数不用在此声明
 * @version 0.1
 * @status Stable
*/

#include "riscv.h"

struct buf;
struct inode;
struct file;
struct context;
struct superblock;
struct stat;
struct proc;


// printf.c
/**
 * @brief 打印格式化内容，与c语言printf类似，仅支持%d, %x, %p, %s, %%, %c；暂剔除调用锁的代码
 * @param fmt 字符指针，要打印的内容
 * @param ... 可变长度参数，格式化替换变量值
*/
void            printf(char* fmt, ...);
/**
 * @brief 程序中止并打印s字符串的内容，暂剔除调用锁的代码
 * @param s 要打印的字符串
*/
void            panic(char* s) __attribute__((noreturn)); // 通知编译器函数从不返回值，如exit()/abort()
/**
 * @brief printf初始化，暂剔除调用锁的代码
*/
void            printfinit(void);


// kalloc.c
/**
 * @brief 初始化内存分配
*/
void            kinit(void);
/**
 * @brief 释放内存，通常释放kalloc申请到的内存，初始化kinit时除外
 * @param pa 指向待释放内存的指针
*/
void            kfree(void *pa);
/**
 * @brief 申请一个4096-byte的页表的物理内存
 * @return 指向申请到的页表的指针，若申请失败返回0
*/
void*           kalloc(void);


// string.c
/**
 * @brief 给指定n大小的内存空间批量赋值
 * @param dst 待赋值的内存开始指针
 * @param c 将赋值的值
 * @param n 赋值空间的大小
 * @return 赋值后的内存开始指针
*/
void*           memset(void* dst, int c, uint n);
/**
 * @brief 类似于strncpy但保证一遇到'\0'就终止复制字符，且将'\0'也计入字符数量n
 * @param s 目标字符指针
 * @param t 源字符指针
 * @param n 复制字符数量
 * @return 复制的结果指针
*/
char*           safestrcpy(char* s, const char* t, int n);
/**
 * @brief 返回字符串长度
 * @param s
 * @return 字符串s的长度
*/
int             strlen(const char *s);
/**
 * @brief 将src指向的字符串挪到dst指向的指针
 * @param dst 目标指针
 * @param src 源指针
 * @return 目标指针
*/
void*           memmove(void* dst, const void* src, uint n);
/**
 * @brief 比较q和p字符串大小
 * @param p 字符串（前）
 * @param q 字符串（后）
 * @param n 比较字符数目
 * @return p大：正数    q大：负数   一样大：0
*/
int             strncmp(const char* p, const char* q, uint n);
/**
 * @brief 从t字符串赋值n个字符到s指向字符串
 * @param s 目标字符串
 * @param t 源字符串
 * @param n 欲复制的字符数量
 * @return 目标字符串指针
*/
char*           strncpy(char *s, const char *t, int n);


// proc.c
/**
 * @brief 初始化进程
*/
void            procinit(void);
/**
 * @brief 初始化第一个进程
*/
void            userinit(void);
/**
 * @brief 进程调度
*/
void            scheduler(void) __attribute__((noreturn));
/**
 * @brief 给每个进程的内核栈申请一个页，将其映射到内存高地址，后面紧跟着一个无效保护页
 * @param kpgtbl 内核页表
*/
void            proc_mapstacks(pagetable_t kpgtbl);
/**
 * @brief 得到当前cpu的id，（此时中断需要禁用以防止进程间竞争挪到其他CPU）
 * @return tp存储的cpuid
*/
int             cpuid(void);
/**
 * @brief 得到当前运行的cpu（结构体），（此时中断需要禁用）
 * @return 当前运行的cpu（结构体）
*/
struct cpu*     mycpu(void);
/**
 * @brief 返回当前运行的进程（结构体指针），若没有返回0
 * @return 当前运行的进程（结构体指针）
*/
struct proc*    myproc(void);
/**
 * @brief 给进程p创建一个不带用户内存的用户页表，但带有trampoline和trapframe页
 * @param p 给定的进程
 * @return 成功创建则返回页表指针，否则返回0
*/
pagetable_t     proc_pagetable(struct proc *p);
/**
 * @brief 释放进程页表并释放其引用的物理内存
 * @param pagetable 待释放的页表
 * @param sz 释放的大小
*/
void            proc_freepagetable(pagetable_t pagetable, uint64 sz);
/**
 * @brief 设置第一个用户进程
*/
void            userinit(void);
/**
 * @brief 扩充或缩减进程的用户内存
 * @param n 扩充的大小(byte)
 * @return 0——成功；-1——失败
*/
int             growproc(int n);
/**
 * @brief 退出当前进程，不返回，该退出的进程在其父进程调用wait()之前保持僵尸进程状态
 * @param status 该进程退出时的状态
*/
void            exit(int status);
/**
 * @brief 等待子进程退出
 * @param addr 存放子进程退出时状态的地址
 * @return 退出的子进程pid
*/
int             wait(uint64 addr);
/**
 * @brief 返回到调度器scheduler()，必须保证proc->state已经改变
*/
void            sched(void);
/**
 * @brief kill给定pid的进程，但函数内不会直接让进程退出，只是设置killed状态为1，直到该进程试图返回用户空间时才会退出
 * @param pid 要kill的进程id
 * @return 找到要kill的进程返回0，否则-1
*/
int             kill(int pid);
/**
 * @brief 将给定进程状态设置为killed
 * @param p 待killed的进程
*/
void            setkilled(struct proc *p);
/**
 * @brief 给定进程是否已被killed
 * @param p 待判定的进程
 * @return 1：是；0：否
*/
int             killed(struct proc *p);
/**
 * @brief 要么向用户地址复制，要么向内核地址复制内容
 * @param user_dst 1:向用户地址复制；0:向内核地址复制
 * @param dst 复制的目标地址
 * @param src 源地址
 * @param len 复制的长度
 * @return 0:复制成功；-1:复制失败
*/
int             either_copyout(int user_dst, uint64 dst, void *src, uint64 len);
/**
 * @brief 要么从用户地址复制，要么从内核地址复制内容
 * @param user_src 1:从用户地址复制；0:从内核地址复制
 * @param dst 复制的目标地址
 * @param src 源地址
 * @param len 复制的长度
 * @return 0:复制成功；-1:复制失败
*/
int             either_copyin(void *dst, int user_src, uint64 src, uint64 len);
/**
 * @brief 用于debug，打印进程状态列表
*/
void            procdump(void);
/**
 * @brief 唤醒chan链上的所有进程
 * @param chan sleep链，即其他进程申请的资源
*/
void            wakeup(void*);
/**
 * @brief 将进程p沉睡
 * @param chain 等待的资源
*/
void            sleep(void* chain/*, struct spinlock**/);
/**
 * @brief 当前进程放弃CPU，切换上下文给其他进程
*/
void            yield(void);


// vm.c
/**
 * @brief 将虚拟地址范围映射到物理地址，并将相应的PTE添加到页表中
 * @param kpgtbl 内核页表的起始地址
 * @param va 起始虚拟地址
 * @param pa 起始物理地址
 * @param sz 映射的大小
 * @param perm 相应的权限
*/
void            kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm);
/**
 * @brief 创建一个空的用户页表
 * @return 返回页表的指针，如果内存不足返回0
*/
pagetable_t     uvmcreate();
/**
 * @brief 从va开始的虚拟地址创建PTE，这些PTE指向从pa开始的物理地址
 * @param pagetable 页表的起始地址
 * @param va 起始虚拟地址
 * @param size 映射的大小
 * @param pa 起始物理地址
 * @param perm 相应的权限
 * @return 0：创建成功   -1：walk()无法分配页表
*/
int             mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm);
/**
 * @brief 释放用户进程的内存页和页表页
 * @param pagetable 用户进程的页表
 * @param sz 用户进程的大小
*/
void            uvmfree(pagetable_t pagetable, uint64 sz);
/**
 * @brief 从虚拟地址va开始，移除npages页的映射，va必须是页对齐的，映射必须存在
 * @param pagetable 页表
 * @param va 虚拟地址
 * @param npages 要移除的页数
 * @param do_free 是否释放物理内存
*/
void            uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free);
/**
 * @brief 将用户的initcode加载到进程的地址0处，用于第一个进程
 * @param pagetable 进程的页表
 * @param src 指向用户initcode的指针
 * @param sz 用户initcode的大小，必须小于一页
*/
void            uvmfirst(pagetable_t pagetable, uchar *src, uint sz);
/**
 * @brief 为进程从oldsz到newsz分配PTE和物理内存
 * @param pagetable 进程的页表
 * @param oldsz 进程的旧大小
 * @param newsz 进程的新大小
 * @param xperm 权限
 * @return 0：分配失败 newsz：分配成功
*/
uint64          uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz, int xperm);
/**
 * @brief 释放用户进程的内存页，以将进程大小从oldsz缩小到newsz
 * @param pagetable 进程的页表
 * @param oldsz 进程的旧大小
 * @param newsz 进程的新大小
 * @return 新进程的大小
*/
uint64          uvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz);
/**
 * @brief 初始化内核页表kernel_pagetable
*/
void            kvminit(void);
/**
 * @brief 将硬件页表寄存器切换到内核页表，并启用分页
*/
void            kvminithart(void);
/**
 * @brief 从用户空间复制数据到内核空间
 * @param pagetable 页表
 * @param dst 目标内存地址
 * @param srcva 源内存地址
 * @param len 要复制的数据长度
 * @return 0：成功 -1：失败
*/
int             copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len);
/**
 * @brief 从用户空间复制一个以'\0'结尾的字符串到内核空间
 * @param pagetable 页表
 * @param dst 目标内存地址
 * @param srcva 源内存地址
 * @param max 最大复制字节数
 * @return 0：成功 -1：失败
*/
int             copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max);
/**
 * @brief 将一个页表项标记为无效，使得用户无法访问该页。它通常被用于为用户栈设置保护页
 * @param pagetable 用户进程的页表
 * @param va 虚拟地址
*/
void            uvmclear(pagetable_t pagetable, uint64 va);
/**
 * @brief 从内核空间复制数据到用户空间
 * @param pagetable 用户进程的页表
 * @param dstva 目标虚拟地址
 * @param src 源数据的指针
 * @param len 要复制的数据长度
 * @return 0：成功 -1：失败
*/
int             copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len);
/**
 * @brief 查找虚拟地址对应的物理地址，如果没有映射则返回0，它只能用于查找用户页
 * @param pagetable 页表
 * @param va 虚拟地址
 * @return 物理地址
*/
uint64          walkaddr(pagetable_t pagetable, uint64 va);
/**
 * @brief 在页表中查找虚拟地址va对应的页表项，如果页表项不存在则根据alloc参数决定是否分配新的页表
 * @param pagetable 页表的起始地址
 * @param va 所需查找的虚拟地址
 * @param alloc 是否需要分配新的页表项
 * @return 所指页表项的指针,若未找到则返回0
*/
pte_t*          walk(pagetable_t pagetable, uint64 va, int alloc);
/**
 * @brief 将一个进程的虚拟地址空间复制到另一个进程的虚拟地址空间，包括页表和物理内存
 * @param old 源进程的页表
 * @param new 目标进程的页表
 * @param sz 源进程的大小
 * @return 0：成功 -1：失败，并释放已映射的物理页
*/
int             uvmcopy(pagetable_t old, pagetable_t new, uint64 sz);


// bio.c
/**
 * @brief 初始化缓冲区缓存，创建一个缓冲区的链表
 * @brief 最近使用的缓冲区在链表头部，最不常用的缓冲区在链表尾部，LRU
*/
void            binit(void);
/**
 * @brief 返回一个缓冲区，其中包含指定块的内容
 * @param dev 设备号
 * @param blockno 块号
 * @return buf 缓冲区
*/
struct buf*     bread(uint dev, uint blockno);
/**
 * @brief 释放一个缓冲区，若当前的引用计数为0，则会被移动到链表头部以表示最近使用
 * @param b 缓冲区
*/
void            brelse(struct buf* b);
/**
 * @brief 将一个缓冲区的内容写入到磁盘中
 * @param b 缓冲区
*/
void            bwrite(struct buf* b);
/**
 * @brief 增加引用计数值
 * @param b 缓冲区
*/
void            bpin(struct buf* b);
/**
 * @brief 减少引用计数值
 * @param b 缓冲区
*/
void            bunpin(struct buf* b);


//virtio_disk.c
/**
 * @brief 初始化virtio，重置缓冲区描述符虚拟队列、空闲ring、used ring，初始化状态寄存器
*/
void            virtio_disk_init(void);
/**
 * @brief 虚拟磁盘读写函数
 * @param b 操作的缓冲区
 * @param write 是否为写设备
*/
void            virtio_disk_rw(struct buf *b, int write);
/**
 * @brief 虚拟 I/O 磁盘设备的中断处理，回收已完成操作的used ring
*/
void            virtio_disk_intr(void);


// swtch.S
/**
 * @brief 切换当前的上下文，将当前的上下文存入old，从new中取出上下文作为当前上下文
 * @param old 存放当前上下文的地址
 * @param new 作为当前上下文的地址
*/
void            swtch(struct context* old, struct context* new);


// syscall.c
/**
 * @brief 获取int类型的系统调用参数
 * @param n 第n个参数
 * @param ip 存放参数的指针
*/
void            argint(int n, int *ip);
/**
 * @brief 获取一个str类型的'\0'结尾的系统调用参数，
 * @param n 第n个参数
 * @param buf 存放参数的指针
 * @param max 字符串最大长度
 * @return 获取的字符串参数长度，若出现错误则返回-1
*/
int             argstr(int n, char* buf, int max);
/**
 * @brief 获取一个指针参数（不检查其合法性，后续过程中会检查）
 * @param n 第n个参数
 * @param ip 存放指针的指针
*/
void            argaddr(int n, uint64* ip);
/**
 * @brief 从当前进程获取位于addr的以'\0'结尾的字符串
 * @param addr 要获取的字符串地址
 * @param buf 存放字符串的指针
 * @param max 获取的字符串最大长度
 * @return 获取的字符串长度，若出现错误返回-1
*/
int             fetchstr(uint64 addr, char* buf, int max);
/**
 * @brief 从当前进程获取位于addr的64位整数（即地址）
 * @param addr 要获取的64位整数的地址
 * @param ip 存放64位整数的指针
 * @return 0:成功      1:失败
*/
int             fetchaddr(uint64 addr, uint64* ip);
/**
 * @brief 系统调用
*/
void            syscall(void);


// exec.c
/**
 * @brief exec系统调用，执行path指向的文件，携带argv作为参数
 * @param path 执行的文件路径
 * @param argv 携带的参数
 * @return 返回携带的参数个数，若出现错误返回-1
*/
int             exec(char *path, char **argv);


// log.c
/**
 * @brief 初始化日志
 * @param dev 设备号
 * @param sb 超级块指针
 */
void            initlog(int dev, struct superblock* sb);
/**
 * @brief 将修改的块从缓存拷贝到日志中

*/
void            log_write(struct buf*);
/**
 * @brief 系统调用时先调用该函数
*/
void            begin_op(void);
/**
 * @brief 系统调用结束前调用该函数。如果这是最后一个FS系统调用操作，提交。
*/
void            end_op(void);


// trap.c
extern uint     ticks;
/**
 * @brief 初始化trap
*/
void            trapinit(void);
/**
 * @brief 设置为在内核中接收异常和陷阱
*/
void            trapinithart(void);
/**
 * @brief 返回用户空间
*/
void            usertrapret(void);


// uart.c
/**
 * @brief 初始化uart各个寄存器
*/
void            uartinit(void);
/**
 * @brief uart中断处理，当输入数据抵达或uart准备好更多输出时调用，被devintr函数调用。
*/
void            uartintr(void);
/**
 * @brief 将字符写入输出缓冲区，通知UART开始发送字符。
 * @brief 若输出缓冲区满则阻塞。只适合在write调用中使用。
 * @param c 输入字符
*/
void            uartputc(int c);
/**
 * @brief uartputc()的变体，不需要使用中断。
 * @brief 内核的printf使用该函数输入字符，它会忙等uart的输出寄存器变为空。
 * @param c 输入字符
*/
void            uartputc_sync(int c);
/**
 * @brief 从UART读入一个输入字符
 * @return 若LSR（line status register）最低位为1，输出RHR寄存器中的值，否则输出-1。
 * @return 最低位表示receive data ready。
*/
int             uartgetc(void);


// plic.c
/**
 * @brief 初始化平台级中断
*/
void            plicinit(void);
/**
 * @brief 设置hart中相应的比特位
*/
void            plicinithart(void);
/**
 * @brief 获取目前应该服务的中断请求
*/
int             plic_claim(void);
/**
 * @brief 告知PLIC正在处理的中断请求
*/
void            plic_complete(int);


// console.c
/**
 * @brief 初始化console
*/
void            consoleinit(void);
/**
 * @brief console输入中断处理程序

*/
void            consoleintr(int);
/**
 * @brief 将字符输送到uart输出寄存器
 * @param c 输入字符
*/
void            consputc(int c);


// file.c
/**
 * @brief 分配文件结构
 * @return 指向文件的指针
*/
struct file*    filealloc(void);
/**
 * @brief 关闭文件；若引用数不为0，则减少引用数，否则关闭
 * @param f 文件
*/
void            fileclose(struct file* f);
/**
 * @brief 增加文件f的引用计数值
 * @param f 文件

*/
struct file*    filedup(struct file* f);
/**
 * @brief 初始化文件
*/
void            fileinit(void);
/**
 * @brief 读文件f
 * @param f 文件
 * @param addr 用户虚拟地址
 * @param n 读取字节数

*/
int             fileread(struct file* f, uint64 addr, int n);
/**
 * @brief 获取文件的元数据
 * @param f 文件
 * @param addr 用户虚拟地址，指向stat结构

*/
int             filestat(struct file* f, uint64 addr);
/**
 * @brief 写文件f
 * @param f 文件
 * @param addr 用户虚拟地址
 * @param n 写字节数

*/
int             filewrite(struct file* f, uint64 addr, int n);


// fs.c
/**
 * @brief 初始化文件系统
 * @param dev 设备号
*/
void            fsinit(int dev);
/**
 * @brief 写一个新的目录条目
 * @param dp 目录地址
 * @param name 文件名
 * @param inum 条目的索引节点数
 * @return 成功返回0，否则返回-1
*/
int             dirlink(struct inode* dp, char* name, uint inum);
/**
 * @brief 查找目录中的目录条目
 * @param dp 目录地址
 * @param name 文件名
 * @param poff 条目的字节偏移

*/
struct inode*   dirlookup(struct inode* dp, char* name, uint* poff);
/**
 * @brief 分配一个索引节点
 * @param dev 设备号
 * @param type 文件类型
 * @return 索引节点指针，没有空闲索引节点则返回NULL
*/
struct inode*   ialloc(uint dev, short type);
/**
 * @brief 引用数加1
 * @param ip 索引节点
 * @return 索引节点
*/
struct inode*   idup(struct inode* ip);
/**
 * @brief 初始化inode
*/
void            iinit();
/**
 * @brief 去除引用数，如果这是最后一个引用，回收索引节点表条目，释放索引节点
 * @param ip 索引节点
*/
void            iput(struct inode* ip);
/**
 * @brief 删除了unlock，现在的功能相当于put

*/
void            iunlockput(struct inode*);
/**
 * @brief 将修改后的索引拷贝到磁盘
 * @param ip 索引节点指针
*/
void            iupdate(struct inode* ip);
/**
 * @brief 字符串比较

*/
int             namecmp(const char*, const char*);

struct inode*   namei(char*);

struct inode*   nameiparent(char*, char*);
/**
 * @brief 读取索引节点的数据
 * @param ip 索引节点
 * @param user_dst 如果为1，dst为用户虚拟地址，否则dst为内核地址
 * @param dst 内核地址或用户虚拟地址
 * @param off 偏移量
 * @param n 读的数据量？
 * @return 成功读取的字节数
*/
int             readi(struct inode* ip, int user_dst, uint64 dst, uint off, uint n);
/**
 * @brief 将路径名中，name之后的部分提取出。示例：当name=‘a’，skipelem("a/bb/c", name) = "bb/c"
 * @param path 路径名
 * @param name 某级目录名
*/
void            stati(struct inode* path, struct stat* name);
/**
 * @brief 将数据写入索引节点
 * @param ip 索引节点
 * @param user_dst 如果为1，dst为用户虚拟地址，否则dst为内核地址
 * @param dst 内核地址或用户虚拟地址
 * @param off 偏移量
 * @param n 写的数据量？
 * @return 成功写的字节数
*/
int             writei(struct inode* ip, int user_dst, uint64 dst, uint off, uint n);
/**
 * @brief 截断索引节点(丢弃内容)
 * @param ip 索引节点
*/
void            itrunc(struct inode* ip);
void            ilock(struct inode*);
void            iunlock(struct inode*);


#define NELEM(x)    (sizeof(x) / sizeof(x[0]))